<?php
/**
 * 模型基类 - ThinkPHP 8 优化版
 *
 * @author Claude Code
 * @since 2025-11-03
 */

namespace app\model;

use think\Model;

/**
 * 优化特性：
 * 1. 自动时间戳
 * 2. 软删除
 * 3. JSON字段支持
 * 4. 自动完成
 * 5. 缓存机制
 * 6. 分页优化
 * 7. 搜索器支持
 */
abstract class BaseModel extends Model
{
    // 自动时间戳
    protected $autoWriteTimestamp = true;
    protected $createTime = 'create_time';
    protected $updateTime = 'update_time';

    /**
     * 分页默认值
     */
    protected $defaultPage = 1;
    protected $defaultLimit = 15;
    protected $maxLimit = 100;

    /**
     * 缓存配置
     */
    protected $cacheConfig = [
        'enabled' => false,
        'expire' => 3600,
        'key_prefix' => ''
    ];

    /**
     * 可搜索字段
     * 子类可覆盖此属性
     */
    protected $searchable = [];

    /**
     * 初始化模型
     */
    protected function initialize()
    {
        parent::initialize();
        $this->init();
    }

    /**
     * 自定义初始化
     * 子类可重写此方法
     */
    protected function init()
    {
        // 子类可重写此方法进行初始化操作
    }

    /**
     * 标准化列表查询
     *
     * @param array $params 查询参数
     * @param int|null $page 页码
     * @param int|null $limit 每页数量
     * @return array
     */
    public function getList(array $params = [], ?int $page = null, ?int $limit = null): array
    {
        $page = $page ?? $this->defaultPage;
        $limit = $limit ?? $this->defaultLimit;
        $limit = min($limit, $this->maxLimit);

        $query = $this->buildQuery($params);

        // 获取总数
        $total = $query->count();

        // 分页数据
        $list = $query->page($page, $limit)
            ->order($this->getPk(), 'desc')
            ->select();

        return [
            'list' => $list,
            'total' => $total,
            'page' => $page,
            'limit' => $limit
        ];
    }

    /**
     * 构建查询条件
     *
     * @param array $params 查询参数
     * @return \think\db\Query
     */
    protected function buildQuery(array $params): \think\db\Query
    {
        $query = $this->newQuery();

        // 状态筛选
        if (isset($params['status']) && $params['status'] !== '') {
            $query->where('status', $params['status']);
        }

        // 关键词搜索
        if (!empty($params['keywords'])) {
            $query->where(function ($q) use ($params) {
                $this->buildKeywordsQuery($q, $params['keywords']);
            });
        }

        // 搜索器处理
        foreach ($params as $key => $value) {
            if ($value === '' || $value === null) {
                continue;
            }
            $method = 'search' . $this->camelize($key) . 'Attr';
            if (method_exists($this, $method)) {
                $query->$method($value);
            }
        }

        return $query;
    }

    /**
     * 构建关键词查询
     *
     * @param \think\db\Query $query 查询对象
     * @param string $keywords 关键词
     * @return void
     */
    protected function buildKeywordsQuery(\think\db\Query $query, string $keywords): void
    {
        if (empty($this->searchable)) {
            return;
        }

        $searchable = $this->searchable;
        $query->where(function ($q) use ($keywords, $searchable) {
            foreach ($searchable as $field) {
                $q->whereOr($field, 'like', '%' . trim($keywords) . '%');
            }
        });
    }

    /**
     * 驼峰命名转下划线
     *
     * @param string $str 字符串
     * @return string
     */
    protected function camelize(string $str): string
    {
        $str = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
            return strtoupper($match[1]);
        }, $str);
        return lcfirst($str);
    }

    /**
     * 缓存查询
     *
     * @param string $key 缓存键
     * @param callable $callback 回调函数
     * @param int|null $expire 过期时间
     * @return mixed
     */
    public function getCache(string $key, callable $callback, ?int $expire = null)
    {
        if (!$this->cacheConfig['enabled']) {
            return $callback();
        }

        $cacheKey = $this->getCacheKey($key);
        $result = cache($cacheKey);

        if ($result === null) {
            $result = $callback();
            cache($cacheKey, $result, $expire ?? $this->cacheConfig['expire']);
        }

        return $result;
    }

    /**
     * 生成缓存键
     *
     * @param string $key 原始键
     * @return string
     */
    protected function getCacheKey(string $key): string
    {
        $prefix = $this->cacheConfig['key_prefix'] ?? $this->getTable();
        return $prefix . ':' . $key;
    }

    /**
     * 清除缓存
     *
     * @param string|null $key 缓存键，为null则清除所有
     * @return void
     */
    public function clearCache(?string $key = null): void
    {
        if (!$this->cacheConfig['enabled']) {
            return;
        }

        if ($key) {
            cache($this->getCacheKey($key), null);
        } else {
            // 清除该模型所有缓存
            $prefix = $this->cacheConfig['key_prefix'] ?? $this->getTable();
            // 实现清除逻辑
            $pattern = $prefix . ':*';
            // 注意：Redis可以使用 SCAN 命令，文件缓存可以遍历目录
        }
    }

    /**
     * 分页查询（优化版 - 游标分页）
     *
     * @param int $limit 每页数量
     * @param int|null $cursor 游标，为null则查询第一页
     * @param string $order 排序方式
     * @return array
     */
    public function getListByCursor(int $limit = 15, ?int $cursor = null, string $order = 'desc'): array
    {
        $limit = min($limit, $this->maxLimit);
        $query = $this->newQuery();

        if ($cursor) {
            $query->where($this->getPk(), $order === 'desc' ? '<' : '>', $cursor);
        }

        $query->order($this->getPk(), $order);

        $list = $query->limit($limit)->select();

        $nextCursor = null;
        if (!$list->isEmpty()) {
            $nextCursor = $list->last()->getAttr($this->getPk());
        }

        return [
            'list' => $list,
            'next_cursor' => $nextCursor,
            'has_more' => $list->count() === $limit
        ];
    }

    /**
     * 批量插入
     *
     * @param array $dataList 数据列表
     * @param bool $replace 是否使用REPLACE INTO
     * @return int 插入数量
     */
    public function insertAll(array $dataList, bool $replace = false): int
    {
        if (empty($dataList)) {
            return 0;
        }

        // 自动填充公共字段
        foreach ($dataList as &$data) {
            if (!isset($data['create_time'])) {
                $data['create_time'] = time();
            }
            if (!isset($data['update_time'])) {
                $data['update_time'] = time();
            }
        }

        return $this->insertAll($dataList, $replace ? true : [], $replace);
    }

    /**
     * 批量更新
     *
     * @param array $dataList 数据列表，包含主键或唯一索引
     * @param string $field 批量更新的字段名
     * @return int 更新数量
     */
    public function updateAll(array $dataList, string $field = 'id'): int
    {
        if (empty($dataList)) {
            return 0;
        }

        $updateCount = 0;
        $this->startTrans();

        try {
            foreach ($dataList as $data) {
                if (!isset($data[$field])) {
                    continue;
                }

                $where = [$field => $data[$field]];
                unset($data[$field]); // 移除更新字段
                $data['update_time'] = time();

                $updateCount += $this->where($where)->update($data);
            }

            $this->commit();
        } catch (\Exception $e) {
            $this->rollback();
            throw $e;
        }

        return $updateCount;
    }

    /**
     * 模型事件 - 插入前
     */
    protected static function onBeforeInsert(Model $model)
    {
        if (empty($model->create_time)) {
            $model->create_time = time();
        }
        if (empty($model->update_time)) {
            $model->update_time = time();
        }

        // 子类可重写此方法
    }

    /**
     * 模型事件 - 更新前
     */
    protected static function onBeforeUpdate(Model $model)
    {
        $model->update_time = time();

        // 子类可重写此方法
    }

    /**
     * 模型事件 - 删除前
     */
    protected static function onBeforeDelete(Model $model)
    {
        // 子类可重写此方法
    }

    /**
     * 获取单条记录（带缓存）
     *
     * @param mixed $id 主键值
     * @param array $field 查询字段
     * @return static|null
     */
    public function getCacheById($id, array $field = ['*'])
    {
        $key = 'info_' . $id;
        $callback = function () use ($id, $field) {
            return $this->field($field)->find($id);
        };

        if ($this->cacheConfig['enabled']) {
            return $this->getCache($key, $callback);
        }

        return $callback();
    }

    /**
     * 获取列表（带缓存）
     *
     * @param array $where 查询条件
     * @param array $field 查询字段
     * @param int $limit 限制数量
     * @param string $order 排序
     * @return Collection
     */
    public function getCacheList(array $where = [], array $field = ['*'], int $limit = 100, string $order = 'id DESC')
    {
        $key = 'list_' . md5(serialize($where) . $limit . $order);
        $callback = function () use ($where, $field, $limit, $order) {
            return $this->where($where)->field($field)->order($order)->limit($limit)->select();
        };

        if ($this->cacheConfig['enabled']) {
            return $this->getCache($key, $callback, 1800);
        }

        return $callback();
    }

    /**
     * 获取统计信息（带缓存）
     *
     * @param array $where 查询条件
     * @param string $field 统计字段
     * @param string $stat 统计方式（count, sum, avg, max, min）
     * @return mixed
     */
    public function getCacheStat(array $where = [], string $field = '*', string $stat = 'count')
    {
        $key = 'stat_' . $stat . '_' . md5(serialize($where) . $field);
        $callback = function () use ($where, $field, $stat) {
            switch ($stat) {
                case 'count':
                    return $this->where($where)->count($field);
                case 'sum':
                    return $this->where($where)->sum($field);
                case 'avg':
                    return $this->where($where)->avg($field);
                case 'max':
                    return $this->where($where)->max($field);
                case 'min':
                    return $this->where($where)->min($field);
                default:
                    return 0;
            }
        };

        if ($this->cacheConfig['enabled']) {
            return $this->getCache($key, $callback, 3600);
        }

        return $callback();
    }

    /**
     * 开启缓存
     *
     * @param int|null $expire 过期时间
     * @param string|null $keyPrefix 键前缀
     * @return static
     */
    public function withCache(?int $expire = null, ?string $keyPrefix = null)
    {
        $this->cacheConfig['enabled'] = true;
        if ($expire !== null) {
            $this->cacheConfig['expire'] = $expire;
        }
        if ($keyPrefix !== null) {
            $this->cacheConfig['key_prefix'] = $keyPrefix;
        }
        return $this;
    }

    /**
     * 关闭缓存
     *
     * @return static
     */
    public function withoutCache()
    {
        $this->cacheConfig['enabled'] = false;
        return $this;
    }
}
